﻿# encoding: UTF-8

import csv
from datetime import datetime, timedelta
import time
# 导入框架提供的函数，我们这里先引入订阅股票回调函数，订阅股票，下单，增加配置参数功能
from strategy_platform.api import (sub_realmd,unsub_realmd,  register_realmd_cb)
from strategy_platform.api import (sub_order, register_order_cb)
from strategy_platform.api import (submit_order,query_order, query_position)
from strategy_platform.api import (add_argument)
#from strategy_trade_api import (register_strategy_exec_stat_cb)
from strategy_platform.strategy_trade_api import start_algo_strategy_trade, register_strategy_exec_stat_cb, sub_strategy_exec_stat,stop_strategy_trade

"""
    本文件是一个策略的最简单用例，包含on_init, on_fini, on_update 三个函数
    订阅一个标的，除此之外什么也不做
"""



# 账户类型和账户名称， 在策略启动时输入真实的账户类型和账户
acct_type = "S0"
acct = "354232"
#eod_file = 'C:\\job\\finance\\csv\\eod.csv'
target_file = 'C:\\job\\finance\\csv\\target.csv'
trade_file = 'C:\\job\\finance\\csv\\trade.csv'

# 策略开始和结束时间，格式为"hh:mm:ss" 在该时间段之内才运行策略
# 如果start_time不输入则策略脚本运行时里面启动策略的运行
# 如果end_time不输入则表示策略一直不退出，直到关闭该运行框架
start_time = "8:30:00"
end_time = "19:55:00"


# 启动时配置账号类型和账号等信息
#add_argument("end of day file", str, 0, eod_file)
add_argument("target file", str, 0, target_file)
add_argument("trade file", str, 0, trade_file)
#add_argument("limit_price", float, 2, limit_price)


def get_trade_stock(code):
	for trade_stock in trade_stocks:
		if (trade_stock['stock_code'] == code):
			return trade_stock
			
def get_snapshot(code):
	for trade_stock in trade_stocks:
		if (trade_stock['stock_code'] == code):
			return trade_stock['realmk_obj']
	return None

def get_open_orders(code):
	for trade_stock in trade_stocks:
		if (trade_stock['stock_code'] == code and trade_stock['order_obj'] != None):
			log.info("order status:{}".format(trade_stock['order_obj'].status ))
			if (trade_stock['order_obj'].status == 0 or trade_stock['order_obj'].status == 1):
				return trade_stock['order_obj']
	return None
			
 
def on_order(result, cb_arg):
    '''
    下单的回调函数，函数名不限，下单操作时需要传入该函数
    :param result: 下单结果，result是一个结构体，包含2个变量
        rc: 如果为0表示下单成功，非0表示下单失败
        resp:下单成功时为订单号，下单失败时为失败原因
    :param cb_arg:  注册时传入的变量，一般不会用到
    :return:
    '''
    #order succeed,order no :S0_354232_990100013910
    #2025-09-12 10:21:44,303 -INFO order succeed,order no :S0_354232_990100101705
    if result.rc != "0":
        log.info("order failed, reason:{}".format(result.resp))
        return
    log.info("order succeed,order no :{}".format(result.resp))
    global order_no
    order_no = result.resp


def on_order_update(order_obj, cb_arg):
#[symbol:600030.SH, orderNo:S0_354232_990100013907, price:30.37, qty:60, side:1, status:2, filledQty:60, avgPrice:30.36, cancelQty:0, orderTime:093458, orderDate:20250912]
#2025-09-12 10:21:44,305 -INFO order update: [symbol:600030.SH, orderNo:S0_354232_990100101705, price:30.25, qty:45, side:2, status:0,qty:45, filledQty:0, avgPrice:0.0, cancelQty:0, orderTime:102144, orderDate:20250912]
#2025-09-12 10:21:48,572 -INFO order update: [symbol:600030.SH, orderNo:S0_354232_990100101705, price:30.25, qty:45, side:2, status:2,qty:45, filledQty:45, avgPrice:30.25, cancelQty:0, orderTime:102144, orderDate:20250912]
	log.info("order update: [symbol:{}, orderNo:{}, price:{}, qty:{}, side:{}, status:{},qty:{}, filledQty:{}, avgPrice:{}, cancelQty:{}, orderTime:{}, orderDate:{}]".format(
	order_obj.symbol, order_obj.orderNo, order_obj.price, order_obj.qty, order_obj.side, order_obj.status, order_obj.qty, order_obj.filledQty, order_obj.avgPrice, order_obj.cancelQty, order_obj.orderTime, order_obj.orderDate))
	
	for trade_stock in trade_stocks:
		if (trade_stock['stock_code'] == order_obj.symbol and trade_stock['acct_type'] == order_obj.acctType and trade_stock['acct'] == order_obj.acct ):
			#time_str = order_obj.orderTime + "000"
			#log.info("time:{}".format(time_str))
			#cost_time = cal_time_diff(time_str)
			if (order_obj.status == 3 or order_obj.status == 4):
				if (order_obj.cancelQty > 0):
					trade_stock['number'] = trade_stock['number'] + order_obj.cancelQty
			#trade_stock['order_obj'] = order_obj
        	
def cal_time_diff(time_str):
    now = datetime.now().timestamp()
    current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
    log.info("current time:{},{}".format(current_time,now))
    
    hour = int(time_str[0:2])
    minute = int(time_str[2:4])
    second = int(time_str[4:6])
    millisecond = int(time_str[6:])
    
    # 创建一个timedelta对象，用于表示毫秒
    milliseconds = timedelta(milliseconds=millisecond)
 
# 获取当前日期
    current_date = datetime.now().date()
 
# 创建一个datetime对象，包含当前的日期和解析出来的时间
    datetime_obj = datetime.combine(current_date, datetime.min.time()) + timedelta(hours=hour, minutes=minute, seconds=second) + milliseconds
 
    stock_timestamp = datetime_obj.timestamp()
    log.info("time:{}".format(stock_timestamp))
    
    stock_time = now - stock_timestamp
    log.info("time diff: {}".format(stock_time))
    return stock_time
    
def on_strategy_trade(strategy_trade_obj, cb_arg):
	log.info("on_strategy_trade symbol : {}, tradeside:{}, targetqty:{}".format(strategy_trade_obj.symbol, strategy_trade_obj.tradeSide, strategy_trade_obj.targetQty))


def on_realmd(realmk_obj, cb_arg):
    '''
    订阅"标的"的回调函数，
    :param realmk_obj: 标的 RealMKData 对象
    :param cb_arg: register_realmd_cb注册时传入的变量，一般不会用到
    :return:
    '''
    #real market data: [symbol:000001.SZ, lastPrice:11.85, hihgPirce:11.88, openPrice:11.83, lowPrice:11.83, time:93454000, volumn:8615105, turnover:102125516.0]

    #cal_time_diff (realmk_obj.time)
    
    #log.info("real market data: [symbol:{}, lastPrice:{}, highPirce:{}, openPrice:{}, lowPrice:{}, time:{}, volumn:{}, turnover:{}]".
    #    format(realmk_obj.symbol, realmk_obj.lastPrice, realmk_obj.highPrice,
    #           realmk_obj.openPrice, realmk_obj.lowPrice, realmk_obj.time, realmk_obj.volume, realmk_obj.turnover))
	
    
    #sleep(1)
    #if realmk_obj.lastPrice > price:
    #    log.info("trigger order: [lastPrice :{}]".format(realmk_obj.lastPrice))
    #    submit_order(acct_type, acct, realmk_obj.symbol, 1, 0, realmk_obj.lastPrice, qty, "", on_order)
    #for trade_stock in trade_stocks:
    #    if (trade_stock['stock_code'] == realmk_obj.symbol and trade_stock['number'] > 0):
    #    	sourceid = "strategy_10001_id_" + str(datetime.now().timestamp())
    #    	log.info("start_algo_strategy_trade:{},number:{}".format(trade_stock['stock_code'] ,trade_stock['number']))
    #    	start_algo_strategy_trade(trade_stock['acct_type'], trade_stock['acct'], trade_stock['stock_code'] , trade_stock['number'], trade_stock['tradeside'], trade_stock['params'], sourceid)
    #    	log.info("start_algo_strategy_trade end")
   #     	trade_stock['number'] = 0
   #     	register_order_cb(on_order_update, None)
    #    	sub_order(trade_stock['acct_type'], trade_stock['acct'])
            
def config_argument(argument_dict):
    for k, v in argument_dict.items():
        log.info("argument: [key: {}, value: {}]".format(k, v))
        #if k == "eod_file":
        #    global eod_file
        #    eod_file = v
        if k == "target_file":
            global target_file
            target_file = v
        elif k == "trade_file":
            global trade_file
            trade_file = v
        else:
            # 可以在这里进行配置项的设置
            pass

def read_csv_to_dict_list(filepath):
    """
    读取CSV文件，返回一个字典列表。
    每个字典代表一行数据，键是CSV的列名。
    """
    data = []
    with open(filepath, mode='r', encoding='utf-8') as csvfile:
        # 使用DictReader读取CSV，它会自动处理表头作为字典的键
        reader = csv.DictReader(csvfile)
        for row in reader:
            data.append(row) # 将字典行添加到列表中
    return data

def get_position():
	pos_list = query_position(acct_type, acct)
	log.info("begin go to print position information")
	eod_stocks = []
	if (pos_list == None or len(pos_list) == 0):
		return eod_stocks
	for i in range(len(pos_list)):
		pos_obj = pos_list[i]
		log.info("position i:{} [symbol:{}, currentQty:{}, direction:{}, longFrozen:{}]".format(i, pos_obj.symbol, pos_obj.currentQty, pos_obj.direction, pos_obj.longFrozen))
		eod_stock = dict()
		eod_stock['acct_type'] = acct_type
		eod_stock['acct'] = acct
		eod_stock['stock_code'] = pos_obj.symbol
		eod_stock['number'] = pos_obj.currentQty
		eod_stocks.append(eod_stock)
		
	log.info("end go to print position information")
	return eod_stocks
	
def cal_trade_stocks():
    #eod_stocks = read_csv_to_dict_list(eod_file)
    eod_stocks = get_position()
    target_stocks = read_csv_to_dict_list(target_file)
	
    global trade_stocks
    trade_stocks = []
    for target_stock in target_stocks:
        in_eod = False
        for eod_stock in eod_stocks:
            if (target_stock['acct']==eod_stock['acct'] and target_stock['stock_code']==eod_stock['stock_code'] and target_stock['number'] != eod_stock['number']):
                in_eod = True
                trade_stock = dict()
                trade_stock['acct_type'] = target_stock['acct_type']
                trade_stock['acct'] = target_stock['acct']
                trade_stock['stock_code'] = target_stock['stock_code']
                if (int(target_stock['number']) > int(eod_stock['number'])):
                    trade_stock['tradeside'] = 1
                    trade_stock['number'] = (int(target_stock['number']) - int(eod_stock['number'])) #//2
                else:
                    trade_stock['tradeside'] = 2
                    trade_stock['number'] = (int(eod_stock['number']) - int(target_stock['number'])) #//2
                trade_stocks.append(trade_stock)
        if (in_eod == False):
            trade_stock = dict()
            trade_stock['acct_type'] = target_stock['acct_type']
            trade_stock['acct'] = target_stock['acct']
            trade_stock['stock_code'] = target_stock['stock_code']
            trade_stock['tradeside'] = 1
            trade_stock['number'] = int(target_stock['number'])	#//2
            trade_stocks.append(trade_stock)

    for eod_stock in eod_stocks:
        in_target = False
        for target_stock in target_stocks:
            if (target_stock['acct'] == eod_stock['acct'] and target_stock['stock_code'] == eod_stock['stock_code']):
                in_target = True
        if (in_target == False):
            trade_stock = dict()
            trade_stock['acct_type'] = eod_stock['acct_type']
            trade_stock['acct'] = eod_stock['acct']
            trade_stock['stock_code'] = eod_stock['stock_code']
            trade_stock['tradeside'] = 2
            trade_stock['number'] = int(eod_stock['number'])	#//2
            trade_stocks.append(trade_stock)
    i = 1
    for trade_stock in trade_stocks:
    	
    	id = "id" + str(i).zfill(6)
    	trade_stock['id'] = id
    	trade_stock['instruction_type'] = 'algo'
    	trade_stock['params'] = 'AlgoType=HX_SMARTVWAP;beginTime=093000;endTime=144500;limitUpDownTrading=false; maxRate=0.2;limitPrice=0;extraParam=remark:hx_fundA_001&splitAmount:6000&ordFreqLmt:249;'
    	print(trade_stock)
    	i = i+1
    	#trade_stock['time'] = datetime.now().timestamp()
    	
    	
		
    fieldnames = ['id', 'acct_type', 'acct', 'stock_code','number', 'tradeside', 'instruction_type', 'params']
    with open(trade_file, 'w', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

        # Write the data rows
        writer.writerows(trade_stocks)
    
    for trade_stock in trade_stocks:
    	trade_stock['order_obj'] = None
    	trade_stock['realmk_obj'] = None
    	trade_stock['sourceid'] = None
    	#hedge(trade_stock['stock_code'])
    	#submit_order(trade_stock['acct_type'], trade_stock['acct'], target_stock['stock_code'], trade_stock['tradeside'], 'Q', 0, trade_stock['number'], "", on_order)
     	
    return
    
def sub_200_stock():
	stock_list1 = []
	for i in range(190):
		symbol = "600" + str(i).zfill(3) + ".SH"
		stock_list1.append (symbol)
		log.info("sub {}".format(symbol))
	beforesub = datetime.now().timestamp()
	log.info("before subscribe:{}".format(beforesub))
	sub_realmd(stock_list1)
	aftersub = datetime.now().timestamp()
	log.info("after subscribe:{}".format(aftersub))
	subtime = aftersub - beforesub
	log.info("subscribe cost time:{}".format(subtime))
    
def on_init(argument_dict):
    '''
    策略启动入口函数，如果不实现，则该策略立马退出

    :param argument_dict: 为一个dict，是策略启动时可能需要的参数，该参数可以通过add_argument进行增加
    :return: 无返回值，该函数执行失败时，需要抛出异常
    '''
    #cal_time_diff ("151611222")
    #sub_200_stock ()
    config_argument(argument_dict)

    # 注册订阅标的的回调函数
    register_realmd_cb(on_realmd, None)

    cal_trade_stocks()
    
    stock_list = []
    for trade_stock in trade_stocks:
    	if (trade_stock['number'] > 0):
    		stock_list.append (trade_stock['stock_code'])
    		log.info("sub {}".format(trade_stock['stock_code']))

    sub_realmd(stock_list)
    # 订阅标的
    #sub_realmd("600030.SH")
    register_strategy_exec_stat_cb(on_strategy_trade, None)
    sub_strategy_exec_stat(acct_type, acct)
    
    register_order_cb(on_order_update, None)
    time.sleep(5)
    for trade_stock in trade_stocks:
        if (trade_stock['number'] > 0):
        	
        	sourceid = "strategy_10001_id_" + str(datetime.now().timestamp())
        	log.info("start_algo_strategy_trade:{},number:{}".format(trade_stock['stock_code'] ,trade_stock['number']))
        	start_algo_strategy_trade(trade_stock['acct_type'], trade_stock['acct'], trade_stock['stock_code'] , trade_stock['number'], trade_stock['tradeside'], trade_stock['params'], sourceid)
        	log.info("start_algo_strategy_trade end")
        	trade_stock['number'] = 0
        	trade_stock['sourceid'] = sourceid
        	
        	sub_order(trade_stock['acct_type'], trade_stock['acct'])
    
    return


def on_fini():
    '''
    策略退出时调用该函数， 如果不实现则默认函数只打印一条日志
    :return: 无返回值，如果失败需要抛出异常
    '''
    stock_list1 = []
    for i in range(190):
    	symbol = "600" + str(i).zfill(3) + ".SH"
    	stock_list1.append (symbol)
    	#log.info("sub {}".format(symbol))
    #unsub_realmd(stock_list1)
    log.info("on_fini called ......")
    for trade_stock in trade_stocks:
    	print(trade_stock)
    	if (trade_stock['sourceid'] != None):
    		stop_strategy_trade(trade_stock['sourceid'])

def on_update(argument_dict):
    '''
    更新策略时调用该函数；
    如果不实现该函数，则系统提供一个默认的空函数
    start_time和end_time在系统内部默认会处理，用户函数也可以处理
    :param argument_dict: 参数为dict
    :return: 需要2个返回值，第一个返回值表示成功或失败，第二个表示原因
       True, "", 成功
       False, "because xxx" 失败
    '''
    log.info("on_update called ......")
    return True, ""

